/**
 * C_BackEnd class
 * Class that support the C_GUI class to do networking calls to the Server (S_BackEnd/S_SocketThread).
 * @author Iain Moug
 * @version 0.1
 */

package client;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Scanner;

import javax.swing.JEditorPane;

public class C_BackEnd implements Runnable {

	/* Variables */
	//Status
	//Nickname
	//messaging
	//recipient
	//sender
	
	private String hostname, nickname, email;
	protected int index, noOfFriends;
	protected ArrayList<String[]> friends;
	protected server.S_BackEnd serverB;
	private String[] statusArray;
	private Hashtable<Integer, JEditorPane> textPanes;
	
	//connection stuff
	// The socket connecting us to the server
	private Socket socket;
	// The streams we communicate to the server; these come
	// from the socket
	private DataOutputStream dout;
	private DataInputStream din;
	
	/* Constructor */
	/**
	 *Default constructor
	 *Tries to get the Client's local IP address and intialises the socket connection to the server on port 5000;
	 *@since 0.1
	 */
	public C_BackEnd() {
				
		try {
		    InetAddress addr = InetAddress.getLocalHost();

		    // Get IP Address
		    byte[] ipAddr = addr.getAddress();

		    // Get hostname
		    setHostname(addr.getHostAddress());
		} 
		catch (UnknownHostException e) {
			e.printStackTrace();
		}
		
		friends = new ArrayList<String[]>();
		
		setTextPanes(new Hashtable<Integer, JEditorPane>());
		
/*		statusArray = new String[4];
		statusArray[0] = "Online";
		statusArray[1] = "Unavailable";
		statusArray[2] = "Busy";
		statusArray[3] = "Offline";*/
		
		//serverB = new server.S_BackEnd();
		
		// Connect to the server
		try {
			// Initiate the connection
			/*socket = new Socket("192.168.0.2", 5000 );*/
			setSocket(new Socket(hostname, 5000));
			System.out.println(getSocket().getLocalPort());
			// We got a connection! Tell the world
			System.out.println("C_BackEnd ("+email+") --> Connected to "+ getSocket() +".");
			// Let's grab the streams and create DataInput/Output streams
			// from them
			System.out.println("C_BackEnd ("+email+") --> Getting "+ getSocket() +"'s InputStream.");
			din = new DataInputStream(getSocket().getInputStream());
			
			System.out.println("C_BackEnd ("+email+") --> Getting "+ getSocket() +"'s OutputStream.");
			dout = new DataOutputStream(getSocket().getOutputStream());
			
		} 
		catch( IOException ie ) { 
			System.out.println( ie ); 
		}		
	}
	
	/* Accessors */
	/**
	 * Accessor for the user's friends.
	 * @since 0.1
	 * @return The ArrayList of String arrays each holding that friend's nickname and local IP address;
	 */
	protected ArrayList<String[]> getFriends() {
		return friends;
	}
	
	/**
	 * Deprecated as it calls serverB (S_BackEnd)
	 * @since 0.1
	 */
/*	protected ArrayList<String[]> getUsers() {
		return serverB.getUsers();
	}*/
	
	/**
	 * Accessor for the user's nickname.
	 * @since 0.1
	 * @return The user's nickname, only possible once it is retrieved from the Server at this point. Local saved copy for automatic login could come later.
	 */
	protected String getNickname() {
		return nickname;
	}
	
	/**
	 * Accessor for the user's local IP address.
	 * @since 0.1
	 * @return The user's local IP address.
	 */
	protected String getHostname() {
		return hostname;
	}
	
	/**
	 * Accessor for the user's email address.
	 * @since 0.1
	 * @return The user's email address.
	 */
	protected String getEmail() {
		return email;
	}
	
	/**
	 * Accessor for the Socket
	 * @since 0.1
	 * @return The Socket used for the connection.
	 */
	public Socket getSocket() {
		return socket;
	}
	
	/**
	 * Accessor that goes through the user's list of friends and saves their nicknames into a new String array the same size as the ArrayList.
	 * @since 0.1
	 * @return The String array of the user's friends names.
	 */
	protected String[] getFriendsString() {
		int size = friends.size();
		System.out.println(size);
		String[] friendNames = new String[friends.size()];

		for (int i = 0; i < getFriends().size(); i++) {
			friendNames[i] = getFriends().get(i)[0];
		}
		return friendNames;
	}
	
	/* Mutators */
	/**
	 * Mutator for the text panes Hashtable needed for the retrieval of individual JEditorPane comonponents used for the outputting of text when received from the Server (S_BackEnd/S_SocketThread)
	 * @since 0.1
	 * @return the Hashtable of the Integer value key and it's related JEditorPane.
	 */
	public Hashtable<Integer, JEditorPane> getTextPanes() {
		return textPanes;
	}

	/**
	 * Mutator for the Socket
	 * @since 0.1
	 * @param socket the given Socket which you wish to replace the current Socket with.
	 */
	public void setSocket(Socket socket) {
		this.socket = socket;
	}

	/**
	 * Mutator for the text panes Hashtable used in the constructor to assign a new Hashtable<Integer, JEditorPane) to textPanes.
	 * @since 0.1
	 * @param textPanes the given Hashtable<Integer, JEditorPane) which you want to replace the current Hashtable<Integer, JEditorPane> with.
	 */
	public void setTextPanes(Hashtable<Integer, JEditorPane> textPanes) {
		this.textPanes = textPanes;
	}

	/**
	 * Mutator for the user's nickname
	 * @since 0.1
	 * @param n the given nickname which you want to replace the current nickname with.
	 */
	protected void setNickname(String n) {
		nickname = n;
	}
	
	/**
	 * Mutator for the user's local IP address
	 * @since 0.1
	 * @param hostname the given local IP address you want to replace the current local IP address with.
	 */
	protected void setHostname(String hostname) {
		this.hostname = hostname;
	}
	
	/**
	 * Mutator for the user's email address.
	 * @since 0.1
	 * @param email the given email address you want to replace the current email address with.
	 */
	protected void setEmail(String email) {
		this.email = email;
	}
	
	/**
	 * Mutator for the user's friends list. Used in the adding of new friends and when the server updates the user's friend list (Possibly deprecated)
	 * @since
	 * @param f the given friend list you want to replace the current friend list with.
	 */
	protected void setFriends(ArrayList<String[]> f) {
		friends = f;
	}
	
	/* Network methods */
	
	protected void sendMessage(JEditorPane eP, String text, String r) {
		System.out.println("C_BackEnd ("+email+") -> Preparing message for transmission...");
		String message= "send, "+ nickname +", "+ r +", "+ text;
		
		System.out.println("C_BackEnd ("+email+") -> "+ message);
		
		System.out.println("C_BackEnd ("+email+") -> Attempting to send client "+ nickname +"'s transmission to client "+ r +".");
		try {
			dout.writeUTF(message);
			System.out.print("C_BackEnd ("+email+") ->  SUCCESS >>> client "+ nickname +"'s transmission to client "+ r +" was sent successfully.");
		} catch (IOException e) {
			System.out.print("C_BackEnd ("+email+") ->  ERROR >>> unable to send client "+ nickname +"'s transmission to client "+ r +".");
			e.printStackTrace();
		}		
	}
	
	protected void sendMessage(JEditorPane eP) {
		System.out.println("Preparing message for transmission...");
		String message= "getFriends ,"+ nickname;
		
		System.out.println("C_BackEnd ("+email+") -> Attempting to request "+ nickname +"'s friend list.");
		try {
			dout.writeUTF(message);
			System.out.print("C_BackEnd ("+email+") -> SUCCESS >>> request sent successfully.");
		} catch (IOException e) {
			System.out.print("C_BackEnd ("+email+") -> ERROR >>> unable to send client "+ nickname +"'s request.");
			e.printStackTrace();
		}		
	}
	
	/**
	 * 
	 * (Deprecated)
	 * @param f
	 * @param index
	 */
	public void updateFriendsList(ArrayList<String[]> f, int index) {
		setFriends(f);
		//serverB.setUserFriends(f, index); // DEPRECATED update to new friend list spec
	}
	
	public void addFriend(String n, String c, String e) {
		String[] friend = {n, c}; 
		getFriends().add(friend);
		noOfFriends++;
		
		String message = "addFriend, "+ nickname +", "+ n;
		
		System.out.println("C_BackEnd --> Attempting to send addFriend request on "+ getSocket() +"'s OutputStream.");
		try {
			dout.writeUTF(message);
			System.out.println("C_BackEnd ("+email+") -> SUCCESS >> addFriend request successfully sent..");
		} catch (IOException e1) {
			System.out.println("C_BackEnd ("+email+") -> Unable to send addFriend request on "+ getSocket() +"'s OutputStream.");
			e1.printStackTrace();
		}
	}
	
	protected int login(String username, String password) {
		
		Scanner usernameScanner = new Scanner(username);
		usernameScanner.useDelimiter("@");
		
		usernameScanner.next();
		if (usernameScanner.hasNext()) {
			setEmail(username);
		}
		else {
			setEmail(username +"@kelvinconnect.com");
		}
		usernameScanner.close();
		
		String message = null;
		try {
			dout.writeUTF("getUserData, "+ email);
			
			message = din.readUTF();
		}
		catch (NullPointerException e) {
			System.out.println("C_BackEnd ("+ email +") -> No server detected, cannot login.");
			return -2;
			// FIXME implement statuses and put user in as offline here.
		}
		catch (IOException e) {
			System.out.println("C_BackEnd ("+ email +") -> couldn't connect to the server.");
			e.printStackTrace();
		}
		
		Scanner messageScanner = new Scanner(message);
		messageScanner.useDelimiter(", ");
		String typeOfMessage = messageScanner.next();
		setNickname(messageScanner.next());
		String userDataPassword = messageScanner.next();
		messageScanner.close();
		
		if (typeOfMessage.compareTo("userData") == 0) {
			if (userDataPassword.compareTo(password) == 0) {
				try {
					dout.writeUTF("loginSuccessful");
				}
				catch (IOException e) {
					System.out.println("C_BackEnd ("+ email  +") -> couldn't connect to the server.");
					e.printStackTrace();
				}
				return 0;
			}
		}
	
		return -1;
		
	}

	protected void startThread() {
		// Start a background thread for receiving messages
		String identifyRequest = null;
		try {
			identifyRequest = din.readUTF();
		} 
		catch (IOException e) {
			System.out.println("C_BackEnd ("+email+") -> couldn't connect to the server.");
			e.printStackTrace();
		}
		Scanner identifyScanner = new Scanner(identifyRequest);
		identifyScanner.useDelimiter(", ");
		
		String request = identifyScanner.next();
		
		if (request.compareTo("identify") == 0) {
			
			try {
				dout.writeUTF("identity, "+ nickname);
			} 
			catch (IOException e) {
				System.out.println("C_BackEnd ("+email+") -> couldn't connect to the server.");
				e.printStackTrace();
			} 
			
		}
		identifyScanner.close();
		
		System.out.println("C_BackEnd ("+email+") --> Sending getFriends command on "+ getSocket() +"'s OutputStream.");
		try {
			dout.writeUTF("getFriends, "+ nickname);
		} catch (IOException e) {
			System.out.println("C_BackEnd ("+email+") -> couldn't connect to the server.");
			e.printStackTrace();
		}
		
		System.out.println("C_BackEnd ("+email+") --> Starting new thread.");
		new Thread(this).start();
	}
	
	// Background thread runs this: show messages from other window
	public void run() {
		try {
			// Receive messages one-by-one, forever
			while (true) {
				// Get the next message
				
				System.out.println("C_BackEnd ("+email+") -> Attempting to read in message on OutputStream.");
				String message = din.readUTF();
				System.out.println("C_BackEnd ("+email+") -> SUCCESS >>> message read in successfully.");
				// Print it to our text window
				System.out.println(message);
				Scanner messageScanner = new Scanner(message);
				messageScanner.useDelimiter(", ");
				
				String segment1 = messageScanner.next();
				String segment2 = messageScanner.next();
				
				if (segment1.compareTo("friendUpdate") == 0) {

					String segment3 = messageScanner.next();
					String[] store = new String[2];
					store[0] = segment2;
					store[1] = segment3;
					
					friends.add(store);

					noOfFriends = friends.size();
					
				}
				else if (segment1.compareTo("message") == 0) {

					String segment3 = messageScanner.next();
					
					int paneIndex = -1;
					for (int i = 0; i < friends.size(); i++) {
						if (friends.get(i)[0].compareTo(segment2) == 0) {
							paneIndex = i;
						}
					}
					
					getTextPanes().get(paneIndex).setText(getTextPanes().get(paneIndex).getText()+segment2 + ": "+ segment3+"\n");
				}
				else if (segment1.compareTo("friendRequest") == 0) {

					String response = C_GUI.friendRequest(segment2);
					response = response +", "+ nickname;
					dout.writeUTF(response); 
				}
				messageScanner.close();
			}
		} 
		catch( IOException ie ) { 
			System.out.println( ie ); 
		}		
	}
}